﻿using System;
using System.Collections.Generic;
using MentalAlchemy.Atomics;

namespace MentalAlchemy.Molecules.MachineLearning
{
	/// <summary>
	/// Class for evolutionary training of the neural network.
	/// 
	/// Note: Objective function should have information about the network structure!
	/// </summary>
	public class NeuroEvolutionaryAlgorithm : EvolutionaryAlgorithm
	{
		/// <summary>
		/// Neural network.
		/// </summary>
		public LayeredNeuralNetwork NeuralNetwork { get; set; }

		/// <summary>
		/// Performs initialization and prepares data structures.
		/// </summary>
		/// <param name="parameters">EA parameters.</param>
		public override void Init(EAParameters parameters)
		{
			if (NeuralNetwork == null) throw new Exception("[NeuroEvolutionaryAlgorithm.Init]: Empty neural network.");

			parameters.IndividualSize = NeuralNetwork.GetTotalConnectionsNumber();
			base.Init(parameters);
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Prepares information for a log-file.
		/// </summary>
		/// <param name="ne"></param>
		/// <param name="eaparams"></param>
		/// <param name="annProps"></param>
		/// <returns></returns>
		public static List<string> GetLogLines (NeuroEvolutionaryAlgorithm ne, EAParameters eaparams, NeuralNetProperties annProps)
		{
			var stats = StructMath.ConvertToStringsList(ne.FitnessStats, true);
			ne.NeuralNetwork.SetConnectionWeights(ne.BestIndividual.Genes);

			var logLines = new List<string>();
			logLines.Add("> EA parameters:");
			logLines.AddRange(eaparams.ToStrings());
			logLines.Add("\n> ANN parameters:");
			logLines.AddRange(annProps.ToStrings());
			logLines.Add("\n> NE run stats:");
			logLines.AddRange(stats);
			logLines.Add("\n> Elapsed time (msec):\t" + ne.ElapsedMilliseconds);
			logLines.Add("\n> Best ANN:");
			logLines.AddRange(ne.NeuralNetwork.Layers[1].ToStringsNorm());
			logLines.Add("\n> Population info:");
			logLines.AddRange(EAElements.ToStrings(ne.Population));

			return logLines;
		}
	}

	public class DecomposableNE : NeuroEvolutionaryAlgorithm
	{
		protected List<AmalgamIDEA> eas;	// sub-algorithms.

		/// <summary>
		/// Performs initialization and prepares data structures.
		/// </summary>
		/// <param name="parameters">EA parameters.</param>
		public override void Init(EAParameters parameters)
		{
			if (NeuralNetwork == null) throw new Exception("[NeuroEvolutionaryAlgorithm.Init]: Empty neural network.");

			// perform ANN decomposition by layers and nodes.
			var lDecomp = NeuralDecomposer.DecomposeByLayers(NeuralNetwork);
			//var lDecomp = NeuralDecomposer.DecomposeByLayers(NeuralNetwork);

			// init sub-EAs.
			eas = new List<AmalgamIDEA>();
			for (int i = 0; i < lDecomp.Length; i++)
			{
				var ea = new AmalgamIDEA();
				parameters.IndividualSize = lDecomp[i];
				ea.Init(parameters);
				eas.Add(ea);
			}
		}

		public override void Evaluate()
		{
			var popSize = eas[0].Population.Count*eas.Count;
			var fitStats = EAElements.Evaluate(eas.ToArray(), FitnessFunction, popSize);
			FitnessStats.Add(fitStats);
		}

		public override void Select(EAParameters parameters)
		{
			EAElements.Select(eas.ToArray(), parameters);
		}

		public override void Cross(EAParameters parameters)
		{
			EAElements.Cross(eas.ToArray(), parameters);
		}

		public override void Mutate(EAParameters parameters)
		{
			EAElements.Select(eas.ToArray(), parameters);
		}

		public override void NextGeneration(EAParameters parameters)
		{
			EAElements.NextGeneration(eas.ToArray(), parameters);
		}
	}

	public class NeuralDecomposer
	{
		/// <summary>
		/// [molecule]
		/// 
		/// Performs ANN weights decomposition by layers.
		/// Returns sizes of subproblems.
		/// </summary>
		/// <param name="net"></param>
		/// <returns></returns>
		public static int[] DecomposeByLayers (LayeredNeuralNetwork net)
		{
			var res = new int[net.Layers.Count - 1];	// skip input layer.

			for (int i = 0; i < res.Length; i++)
			{
				res[i] = net.Layers[i + 1].GetWeightsCount();
			}

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Performs ANN weights decomposition by layers.
		/// Returns sizes of subproblems.
		/// </summary>
		/// <param name="layer"></param>
		/// <returns></returns>
		public static int[] DecomposeByNodes (NeuralLayer layer)
		{
			var res = new int[layer.Nodes.Count];

			for (int i = 0; i < res.Length; i++)
			{
				res[i] = layer.Nodes[i].Weights.Length + 1;	// to take into account the bias.
			}

			return res;
		}
	}
}
